package zhqs;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.Security;

/**
 * 绿米token加密工具类
 * menglingwei
 */
public class LvmiTokenEncry {

    //加密方式
    private static String KEY_ALGORITHM = "AES";
    //避免重复new生成多个BouncyCastleProvider对象，因为GC回收不了，会造成内存溢出。
    private static boolean initialized = false;
    //AES初始向量
    private static byte[] ivByte = {0x17, (byte) 0x99, 0x6d, 0x09, 0x3d, 0x28, (byte) 0xdd, (byte) 0xb3, (byte) 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58, 0x56, 0x2e};

    /**
     * 测试
     *
     * @param args
     * @throws UnsupportedEncodingException
     */
    public static void main(String[] args) throws Exception {
        String str = LvmiTokenEncry.encry("1234567890abcdef", "0987654321qwerty");
        System.out.println(str);
    }

    /**
     * 绿米KEY加密方法
     *
     * @param token 待加密内容
     * @param key   密钥KEY
     * @return 返回加密后的数据
     *
     * 网关执行KEY加密步骤：
     * 注意：“key”为32个字节长度的字符串。当网关启用了加密模式时，会对该key进行解密并校验，
     * 以验证写命令的合法性。该“key”的生成规则是：用户收到心跳“heartbeat”里的16个字节的“token”字符串之后，
     * 使用网关的KEY（在APP里获取的随机KEY）对该字符串进行AES-CBC 128加密，生成16个字节的密文后，再转换为32个字节的ASCII码字符串。
     * 例如：
     * 从开放平台网站中获取16个字符长度的随机KEY为“0987654321qwerty“，
     * ”token”为”1234567890abcdef”，
     * 加密后的密文是：0x3E,0xB4,0x3E,0x37,0xC2,0x0A,0xFF,0x4C,0x58,0x72,0xCC,0x0D,0x04,0xD8,0x13,0x14。
     * 那么，”key”为：”3EB43E37C20AFF4C5872CC0D04D81314”。
     */
    public static String encry(String token, String key) throws Exception {
        // token
        byte[] originalContent = token.getBytes("UTF-8");
        // 网关key
        byte[] encryptKey = key.getBytes("UTF-8");
        // 执行加密
        byte[] strByte = LvmiTokenEncry.encryptAES(originalContent, encryptKey, ivByte);
        // 将16位加密数据转为ASCII字符串
        String str = LvmiTokenEncry.bytesToHexString(strByte);
        return str.toUpperCase();
    }

    /**
     * 加密
     *
     * @param originalContent 加密内容
     * @param encryptKey      加密KEY
     * @param ivByte          向量
     * @return
     */
    private static byte[] encryptAES(byte[] originalContent, byte[] encryptKey, byte[] ivByte) throws Exception {
        initialize();

        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        SecretKeySpec skeySpec = new SecretKeySpec(encryptKey, "AES");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(ivByte));
        byte[] encrypted = cipher.doFinal(originalContent);
        return encrypted;
    }

    /**
     * AES解密
     * 填充模式AES/CBC/NoPadding
     * 解密模式128
     *
     * @param content 目标密文
     * @return
     * @throws Exception
     */
    private static byte[] decryptAES(byte[] content, byte[] aesKey, byte[] ivByte) throws Exception {
        initialize();

        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        Key sKeySpec = new SecretKeySpec(aesKey, "AES");
        cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte));// 初始化
        byte[] result = cipher.doFinal(content);
        return result;
    }

    /**
     * BouncyCastle作为安全提供，防止加密解密时候因为jdk内置的不支持改模式运行报错。
     **/
    private static void initialize() {
        if (initialized)
            return;
        Security.addProvider(new BouncyCastleProvider());
        initialized = true;
    }

    // AES生成iv
    private static AlgorithmParameters generateIV(byte[] iv) throws Exception {
        AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
        params.init(new IvParameterSpec(iv));
        return params;
    }

    // 比特数组转为ASCII字符串
    private static String bytesToHexString(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder("");
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }

}